package cn.liwq.demo.web.handler;

import cn.liwq.demo.web.annotation.FooRequestMapping;
import cn.liwq.demo.web.annotation.FooRequestParam;
import org.apache.log4j.Logger;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;

/**
 * <p>Project: demo-common </p>
 * <p>Function:  </p>
 * <p>Description:  </p>
 * <p>Copyright: Copyright(c) 2018 Ucarinc </p>
 * <p>Company: Ucarinc </p>
 *
 * @author weiqiang.li@ucarinc.com
 * @version 1.0
 * @date 2018-08-24 11:06:11
 */
public class ControllerHandler {

    private static final Logger log = Logger.getLogger(ControllerHandler.class);

    public void handle(ServletRequest request, ServletResponse response, Class<?> controllerCls) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;

        // get path
        String path = getApiPath(httpServletRequest, controllerCls);

        try {
            // get method
            Method method = getInvokeMethod(path, controllerCls);
            // get params
            Object[] args = getParameters(httpServletRequest, httpServletResponse, method);
            String result = (String) executeMethd(controllerCls.newInstance(), method, args);
            response.getOutputStream().write(result.getBytes());
        } catch (Exception e) {
            log.error("", e);
        }
    }

    private Object executeMethd(Object instance, Method method, Object[] args)
            throws InvocationTargetException, IllegalAccessException {
        return method.invoke(instance, args);
    }

    private String getApiPath(HttpServletRequest httpServletRequest, Class<?> controllerCls) {
        String controllerPath = controllerCls.getAnnotation(FooRequestMapping.class).value();
        String contextPath = httpServletRequest.getContextPath();
        String uri = httpServletRequest.getRequestURI();
        uri = uri.substring(uri.indexOf(contextPath) + contextPath.length());
        String path = null;
        if (controllerPath.equals("") || controllerPath.equals("/")) {
            path = uri;
        } else {
            int pos = uri.indexOf(controllerPath);
            path = uri.substring(pos + controllerPath.length());
        }
        log.info("path=" + path);
        return path;
    }

    private Method getInvokeMethod(String path, Class<?> controller) {
        Method[] methods = controller.getDeclaredMethods();
        Method invokeMethod = null;
        for (Method method : methods) {
            if (method.isAnnotationPresent(FooRequestMapping.class)) {
                String methodMapping = method.getAnnotation(FooRequestMapping.class).value();
                if (path.equals(methodMapping)) {
                    invokeMethod = method;
                }
            }
        }
        if (null == methods) {
            throw new RuntimeException(String.format("can not find method to handle path: %s", path));
        }
        return invokeMethod;
    }

    private Object[] getParameters(HttpServletRequest request, HttpServletResponse response, Method method) {
        Parameter[] parameters = method.getParameters();
        List<Object> realParams = new ArrayList<>();
        if (parameters.length == 0) {
            return realParams.toArray();
        }
        for (Parameter parameter : parameters) {
            log.info(String.format("param: %s, %s", parameter.getType().getName(), parameter.getName()));
            if (ServletRequest.class.isAssignableFrom(parameter.getType())) {
                realParams.add(request);
            } else if (ServletResponse.class.isAssignableFrom(parameter.getType())) {
                realParams.add(response);
            } else if (parameter.isAnnotationPresent(FooRequestParam.class)) {
                realParams.add(request.getParameter(parameter.getName()));
            } else {
                throw new RuntimeException(String.format("can not handle param mapping [%s]", parameter.getName()));
            }
        }
        return realParams.toArray();
    }

}
